Skip to content

Release 0.3.1: signup, subdomains dataset, Authlib OAuth - #17

Merged
yudelevi merged 15 commits into
mainfrom
development
Sep 2, 2026
Merged

Release 0.3.1: signup, subdomains dataset, Authlib OAuth#17
yudelevi merged 15 commits into
mainfrom
development

Conversation

@yudelevi

@yudelevi yudelevi commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

Ships 0.3.1 to main.

  • discolike.signup() / async_signup() and discolike signup open an account from a work email with no credential, so an agent can onboard a user instead of dead-ending on "get an API key". auth login offers it when no account exists.
  • AppendParams.dataset accepts subdomains.
  • OAuth token requests go through Authlib 1.8.0 instead of hand-rolled request building. Bearer handling, proactive refresh, the 401 replay, persistence and error types are unchanged; refreshes use a dedicated token-endpoint client, so a transport set on http_client= no longer reaches the token endpoint.

517 tests pass, ruff clean.

Greptile Summary

The release adds credential-free SDK and CLI signup, support for the subdomains append dataset, and Authlib-backed OAuth token handling.

  • Exposes synchronous and asynchronous signup APIs plus an interactive CLI flow.
  • Routes signup through an absolute URL derived from the selected API base URL.
  • Migrates PKCE exchange and refresh requests to Authlib.
  • Updates generated request types, package versions, dependencies, documentation, and tests.

Confidence Score: 4/5

The PR is not yet safe to merge because credential saves and logout can erase the signup-email safeguard and allow a different-address signup without confirmation.

The remembered signup address shares the configuration file with credentials, but save_credential replaces that file without signup_email and delete_credential deletes it or retains only the OAuth registration, so ordinary login, refresh persistence, or logout removes the state used to require confirmation for a later address.

Files Needing Attention: packages/discolike/src/discolike/_config.py

Important Files Changed

Filename Overview
packages/discolike/src/discolike/signup.py Adds validated sync and async signup requests and correctly honors the explicit base URL for injected clients.
packages/discolike/src/discolike/_config.py Adds persisted signup-email state, but existing credential save and deletion operations still erase that state.
packages/discolike/src/discolike/_oauth.py Replaces manual PKCE token exchange and refresh handling with Authlib while preserving SDK credential conversion and redacted errors.
packages/discolike/src/discolike/_auth.py Delegates synchronous and asynchronous refreshes to dedicated Authlib token clients while retaining locking, adoption, replay, and persistence behavior.
packages/discolike-cli/src/discolike_cli/signup.py Adds the signup command and confirmation flow for changing the locally remembered email.
packages/discolike-cli/src/discolike_cli/auth.py Offers interactive signup before OAuth login when the user indicates they have no account.
packages/discolike/src/discolike/_generated/requests.py Extends the generated append dataset contract with subdomains.

Reviews (2): Last reviewed commit: "fix(sdk): signup always posts to base_ur..." | Re-trigger Greptile

yudelevi and others added 14 commits August 30, 2026 08:42
Authlib 1.8.0 (2026-08-30) ships httpx2 support, so PKCE challenge
generation, the authorization URL, code exchange, and refresh now go
through its OAuth2Client instead of hand-rolled request building. The
point is to stop owning protocol code: Authlib maintains RFC 6749/7636
handling, we keep only what it lacks.

What stays ours, deliberately:
- DiscolikeAuth (httpx2.Auth): single 401 replay, cross-process
  credential reload (PropelAuth revokes the whole refresh family on a
  replayed refresh token, so two CLI processes must not both refresh),
  sync+async locks, and the API-key branch. Authlib's sync client has
  no lock and refreshes on expiry only.
- Dynamic client registration and metadata discovery: Authlib has no
  RFC 7591 client (authlib/authlib#526, open since 2023).

TokenClient/AsyncTokenClient override parse_response_token so error
types, status codes, and token-redacted payloads are unchanged for
callers. Refreshes use a dedicated token-endpoint client (30s timeout)
rather than the SDK http_client, so proxies configured there no longer
reach the token endpoint; noted in the changelog.

Verified live against prod: login (reused DCR client), status, usage,
proactive refresh with token rotation, and 401 replay.

Also: README sign-up links to discolike.com/signup.
refactor(sdk): hand OAuth token requests to Authlib 1.8.0 (0.3.1)
Regenerated request models from the platform spec: AppendParams.dataset
now allows "subdomains" (up to 300 known subdomains per domain).
Module-level rather than a client method because the client requires a
credential at construction and signup is the one call that has none.
Maps 409 to ValidationError since raise_for_status had no branch for
it and account-exists is a validation-style failure, not a generic error.
The only command that works before auth login, on purpose: it is how an
agent opens the account its human will later log into.
Without this, an agent (or a bug in one) could loop discolike signup /
discolike.signup() over arbitrary emails from one machine with nothing
in the way. Remembering the last signed-up email and requiring an
explicit override before switching adds friction to that specific
failure mode without touching normal single-account use.
A person with no account was bounced straight into a browser OAuth
flow that has nothing to authenticate against. Asking up front and
routing "no" into the same signup path discolike signup already uses
avoids that dead end without touching the existing login flow for
everyone who already has a key or credential.
A bad name currently only fails after a round trip to the API. Mirroring
PropelAuth's real rule client-side (probed and pinned in the API task)
means an agent gets the rejection immediately, with the same wording,
instead of waiting on a network call to learn the same thing.
The interactive account question only checked the login subcommand's
own --api-key, so `discolike --api-key X auth login` still got asked
and, on "no", discarded a key that was already good. It needed the
same option-source check the API-key login path itself already uses.
Also dedupes _is_interactive, which had drifted into two copies.
get_parameter_source for "method" was read off the root Click context,
which only tracks root-level options, so a --method passed to the login
subcommand was never detected as explicit and the account question still
fired for `auth login --method api_key`. Read it off the subcommand's own
context instead.

Also validate --method before offering signup, so an invalid method
errors immediately instead of asking the account question first.

Restores an assertion dropped from
test_login_forged_error_callback_cannot_evict_stored_client during an
earlier commit on this branch.
The punctuation whitelist rejected real names PropelAuth itself
accepts (digits, parentheses, underscores, an email-shaped name).
Its only actual requirement is length; ours now adds just the two
things worth blocking client-side before a round trip: no
angle-bracket/control-character injection, and at least one letter.
CI type-checks the tests directories.
Comment thread packages/discolike/src/discolike/signup.py Outdated
An injected http_client is transport only. A bare one had no base URL at
all, and one pointed elsewhere would have taken the signup body with it.
@yudelevi
yudelevi merged commit bfe2a6d into main Sep 2, 2026
14 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants